# 脚本用于将求解器求出的可读性较差的矩阵转译成指定格式表格

# 前半段需要像solve.py那样读一遍原始数据得到index和id的对应关系
# pandas
import coptpy as copt
import pandas as pd

# copt
import coptpy as cp
from coptpy import COPT

from collections import defaultdict


# 读取csv
def read_csv(filename):
    try:
        df = pd.read_csv(filename, encoding='utf-8')
    except UnicodeDecodeError:
        # 如果 utf-8 编码失败，尝试使用其他编码
        # 或者使用 'latin1' 或 'gbk'
        df = pd.read_csv(filename, encoding='ISO-8859-1')
    return df


data_parser_folder = 'data_parser/'

# 接下来是各种图的信息，先读节点信息
df_node = read_csv(data_parser_folder + 'opt_node.csv')

# 再读边信息
df_edge = read_csv(data_parser_folder + 'opt_edge.csv')

# 最后读距离时效信息
df_order = read_csv(data_parser_folder + 'opt_order.csv')

# 开始为计算需要的各种变量赋值
# 以下变量公式中i j下标从1开始，为了代码方便，下面的i j都从0开始
# 直接已知量（可以读出来）
Dx = defaultdict(dict)  # CDC与RDC间的距离
Tx = defaultdict(dict)  # CDCi向RDCj调拨用时
Dy = defaultdict(dict)  # RDC与门店间的距离
Ty = defaultdict(dict)  # RDCi向门店j调拨用时
IT = defaultdict(dict)  # 从CDC仓库(i)到门店(j)的调拨用时是否小于10小时
M = {}  # 门店进口水果需求
N = {}  # 门店内陆水果需求
F = {}  # 仓库开设成本
G = {}  # 仓库处置成本
S = {}  # 仓库最大处置量
# 间接已知量（可以计算出来）
K1 = 0  # RDC的数量
K2 = 0  # 门店的数量

# 下面的ID均指文件中的原始Id，Index均指连续编号
CDCId2Index = {}  # CDCId到Index的映射
Index2CDCId = {}  # Index到CDCId的映射

RDCId2Index = {}  # RDCId到Index的映射
Index2RDCId = {}  # Index到RDCId的映射

DCId2Index = {}  # DCId到Index的映射
Index2DCId = {}  # Index到DCId的映射

STROEId2Index = {}  # STROEId到Index的映射
Index2STROEId = {}  # Index到STROEId的映射
Id2Type = {}  # Id到Type的映射

# 未知量在后面

# 已知量赋值
# 从node获得F G S K1 K2以及映射关系
for _, row in df_node.iterrows():
    id = int(row['Id'])
    type = row['Type']
    Id2Type[id] = type
    if type == 3:
        STROEId2Index[id] = K2
        Index2STROEId[K2] = id
        K2 += 1
    elif type == 2:
        RDCId2Index[id] = K1
        Index2RDCId[K1] = id
        K1 += 1
    elif type == 0:
        # 内陆CDC
        CDCId2Index[id] = 0
        Index2CDCId[0] = id
    elif type == 1:
        # 港口CDC
        CDCId2Index[id] = 1
        Index2CDCId[1] = id

# 合并RDCId2Index和CDCId2Index得到DCId2Index
# 同时把重复的重新编号加到后面
DCId2Index = {**RDCId2Index,
              Index2CDCId[0]: K1, Index2CDCId[1]: K1+1}
# 合并Index2CDCId和Index2RDCId得到Index2DCId
Index2DCId = {**Index2RDCId,
              K1: Index2CDCId[0], K1+1: Index2CDCId[1]}

# 重新回炉一遍node把F G S对应好
for _, row in df_node.iterrows():
    id = int(row['Id'])
    type = row['Type']
    # 填充F G S
    if type == 3:
        continue

    F[DCId2Index[id]] = row['Opening_fee']
    G[DCId2Index[id]] = row['Processing_fee']
    S[DCId2Index[id]] = row['Capacity']

# 从order获得M和N
for _, row in df_order.iterrows():
    id = row['Id']
    N[STROEId2Index[id]] = row['dm_qty']
    M[STROEId2Index[id]] = row['im_qty']
# 从edge获得Dx Tx Dy Ty
for _, row in df_edge.iterrows():
    fromId = row['From']
    toId = row['To']

    # 根据类型决定填充谁，有CDC向RDC和CDC向门店和RDC向门店三种情况
    if Id2Type[fromId] <= 1 and Id2Type[toId] == 2:
        # CDC向RDC
        Dx[CDCId2Index[fromId]][RDCId2Index[toId]] = row['Distance']
        Tx[CDCId2Index[fromId]][RDCId2Index[toId]] = row['Duration']
    elif Id2Type[fromId] <= 1 and Id2Type[toId] == 3:
        # CDC向门店
        Dy[DCId2Index[fromId]][STROEId2Index[toId]] = row['Distance']
        Ty[DCId2Index[fromId]][STROEId2Index[toId]] = row['Duration']
    elif Id2Type[fromId] == 2 and Id2Type[toId] == 3:
        # RDC向门店
        Dy[DCId2Index[fromId]][STROEId2Index[toId]] = row['Distance']
        Ty[DCId2Index[fromId]][STROEId2Index[toId]] = row['Duration']

# 赋值IT
for DCIndex in Index2DCId:
    for STOREIndex in Index2STROEId:
        if (Ty[DCIndex][STOREIndex] >= 10*60):
            IT[DCIndex][STOREIndex] = 1
        else:
            IT[DCIndex][STOREIndex] = 0

# 读取原始表格的核心函数
def parse_sol_file_with_precision(filename, decimal_places=2):
    solution = {}
    format_str = f"{{:.{decimal_places}f}}"

    with open(filename, 'r') as file:
        for line in file:
            # 处理目标值
            if line.startswith('# Objective value'):
                objective_value = float(line.split()[-1])
                formatted_objective_value = float(
                    format_str.format(objective_value))
                solution['Objective_value'] = formatted_objective_value
                continue

            # 忽略其他注释和空行
            if line.startswith('#') or not line.strip():
                continue

            parts = line.split()
            if len(parts) == 2:
                variable_name = parts[0]
                value = float(parts[1])

                # 提取变量名和索引
                var_name = variable_name.split('(')[0]
                indices = variable_name.split('(')[1].rstrip(')').split(',')

                # 判断是否为一维向量
                if len(indices) == 1:
                    index = int(indices[0])
                    if var_name not in solution:
                        solution[var_name] = {}
                    solution[var_name][index] = float(format_str.format(value))
                else:
                    indices = tuple(map(int, indices))
                    if var_name not in solution:
                        solution[var_name] = {}
                    solution[var_name][indices] = float(format_str.format(value))
                    #if var_name == 'T_y' and float(format_str.format(value))!=0:
                    #    print(f'T_y{indices} = {float(format_str.format(value))}')

    return solution

# 自制的quicksum，copt的那个求出来是表达式，不是真正的求和
def quicksum(expression):
    """
    Compute the sum of values generated by the expression.
    
    Parameters:
    expression (iterable): An iterable expression to sum over.
    
    Returns:
    float: The sum of the values.
    """
    return sum(expression)

#读取solution
solution = parse_sol_file_with_precision('lp_ex1.sol')

#接下来定义输出结果需要的信息
# 先是附件9需要的
opened_DC_num=0#开仓总数
time_efficiency_rate=0#时效满足率
total_lgistics_cost=0#物流总成本
total_inter_DC_cost=0#仓库间中转成本
total_DC_to_store_cost=0#仓库送到门店成本
DC_opening_cost=0#开仓成本
total_processing_cost=0#货物处置成本
total_smart_syste_cost=0#智能系统成本

#开始计算
money_scale=0.0001#货币单位
#计算开仓总数
for key, value in solution['l'].items():
    opened_DC_num+=value
opened_DC_num+=2
#取整数
opened_DC_num=int(opened_DC_num)

#计算时效满足率
f_up = quicksum(solution['T_y'][i, k] for i in range(K1+2) for k in range(K2))
f_down = quicksum(M[k] + N[k] for k in range(K2))
time_efficiency_rate = f_up / f_down

#计算物流总成本
total_lgistics_cost = solution['Objective_value']*money_scale

#计算仓库间中转成本
total_inter_DC_cost = quicksum(0.6 * solution['x'][i, j] * Dx[i][j] for i in range(2) for j in range(K1))*money_scale

#计算仓库送到门店成本
total_DC_to_store_cost = quicksum(
    1.25 * solution['y'][j, k] * Dy[j][k] for j in range(K1+2) for k in range(K2))*money_scale

#计算开仓成本
DC_opening_cost = quicksum(F[i] * solution['l'][i] for i in range(K1))*money_scale

#计算货物处置成本
term4 = quicksum(0.5*G[i]*solution['H'][i] for i in range(K1+2))
term5 = quicksum((solution['p'][i] - solution['H'][i])*G[i]
                    for i in range(K1+2))

total_processing_cost = (term4 + term5)*money_scale

#计算智能系统成本
total_smart_syste_cost = quicksum(
    solution['h'][i] for i in range(K1+2))*10000*money_scale

data={
    "开仓数量":opened_DC_num,
    "时效满足率":time_efficiency_rate,
    "总物流成本": total_lgistics_cost,
    "仓库之间的运输总成本": total_inter_DC_cost,
    "仓库到门店的运输总成本": total_DC_to_store_cost,
    "开仓总成本": DC_opening_cost,
    "出入仓处置总成本": total_processing_cost,
    "智能调度系统采购总成本": total_smart_syste_cost
}

# 将字典转换为 pandas DataFrame
df = pd.DataFrame([data])

print('writing to output_result/Q1-result1.csv...')
# 将 DataFrame 保存为 .xlsx 文件
df.to_csv("output_result/Q1-result1.csv", index=False)